package com.zeroturnaround.licensing;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.security.Signature;
import java.util.Map;

import org.apache.commons.io.IOUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.zeroturnaround.LicenseFileException;

public class UserLicense
  implements Serializable
{
  private static final Logger logger = LoggerFactory.getLogger(UserLicense.class);
  static final long serialVersionUID = 1L;
  private byte[] signature;
  private byte[] license;
  private transient Map dataMap;

  public byte[] getSignature()
  {
    return this.signature;
  }

  public void setSignature(byte[] signature) {
    this.signature = signature;
  }

  public byte[] getLicense() {
    return this.license;
  }

  public void setLicense(byte[] license) {
    this.license = license;
  }

  public Map getData() {
    return this.dataMap;
  }

  private Map findData() {
    try {
      ByteArrayInputStream bais = new ByteArrayInputStream(this.license);
      ObjectInputStream ois = new ObjectInputStream(bais);
      return (Map)ois.readObject();
    } catch (IOException e) {
      logger.debug("License bytes not available", e);
    } catch (ClassNotFoundException e) {
      logger.debug("License bytes are corrupted?", e);
    }
    return null;
  }

  public boolean selfVerify(PublicKey pubKey)
  {
    try
    {
      BouncyCastleProvider bcp = new BouncyCastleProvider();
      Signature sig = Signature.getInstance("SHA1withRSA", bcp);
      sig.initVerify(pubKey);
      sig.update(this.license);
      return sig.verify(this.signature);
    } catch (GeneralSecurityException e) {
      logger.debug("Something is wrong and the verification is indeterminate", e);
    }
    return false;
  }

  public byte[] saveInstance()
  {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try
    {
      ObjectOutputStream oos = new ObjectOutputStream(baos);
      oos.writeObject(this);
      oos.reset();
      oos.close();
    } catch (IOException e) {
      logger.debug("Unable to return serialized UserLicense, what?");
    }
    return baos.toByteArray();
  }

  public void saveInstance(File dstFile) throws IOException
  {
    ObjectOutputStream oos = null;
    try {
      OutputStream out = new FileOutputStream(dstFile);
      oos = new ObjectOutputStream(out);
      oos.reset();
      oos.writeObject(this);
    }
    finally {
      IOUtils.closeQuietly(oos);
    }
  }

  public static UserLicense loadInstance(File srcLicenseFile, PublicKey pubKey) throws LicenseFileException
  {
    ObjectInputStream ois = null;
    try {
      InputStream in = new FileInputStream(srcLicenseFile);
      ois = new ObjectInputStream(in);
      UserLicense ul = (UserLicense)ois.readObject();
      init(ul, pubKey);
      return ul;
    } catch (IOException e) {
      throw new LicenseFileException("Problem accessing license file", e);
    } catch (ClassNotFoundException e) {
      throw new LicenseFileException("Problem parsing license file", e);
    }
    finally {
      IOUtils.closeQuietly(ois);
    }
  }

  public static UserLicense loadInstance(byte[] licenseBytes, PublicKey pubKey) throws LicenseFileException
  {
    try {
      ByteArrayInputStream bais = new ByteArrayInputStream(licenseBytes);
      ObjectInputStream ois = new ObjectInputStream(bais);
      UserLicense ul = (UserLicense)ois.readObject();
      init(ul, pubKey);
      return ul;
    } catch (IOException e) {
      throw new LicenseFileException("Problem accessing license file", e);
    } catch (ClassNotFoundException e) {
      throw new LicenseFileException("Problem parsing license file", e);
    }
  }

  private static void init(UserLicense ul, PublicKey pubKey) {
    if (!ul.selfVerify(pubKey))
      throw new SecurityException("Unable to load license. License verification failed.");
    ul.dataMap = ul.findData();
  }
}